/*
 Xern Entity Relocation Network
 Copyright (C) 2011 Ronie Salgado <roniesalg@gmail.com>

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
*/

#include <boost/make_shared.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/uuid/uuid_io.hpp>

#include "Xern/IGeometryInstance.hpp"
#include "Xern/IClientModule.hpp"
#include "Xern/IProceduralPrimitive.hpp"
#include "Xern/IRenderer.hpp"

#include "Xern/Log.hpp"
#include "Xern/Exceptions.hpp"
#include "Xern/Engine.hpp"
#include "Xern/Plane.hpp"
#include "Xern/WorldManager.hpp"

#include "ClientSession.hpp"
#include "Conversions.hpp"

namespace XernMetaverse
{
	// Object primitive code.
	enum PCode
	{
		PC_NONE = 0,
		PC_PRIM = 9,
		PC_AVATAR = 47,
		PC_GRASS = 95,
		PC_NEWTREE = 111,
		PC_PARTICLES = 143,
		PC_TREE = 255,
	};

	enum ExtraParamsType
	{
		EPT_FLEXIBLE = 0x10,
		EPT_LIGHT = 0x20,
		EPT_SCULPT = 0x30,
	};

	// Sculpt texture downloaded callback.
	class SculptTextureDownloadedCallback: public IResourceLoadedCallback
	{
	public:
		SculptTextureDownloadedCallback(boost::shared_ptr<IProceduralPrimitive> primitive, int type);
		~SculptTextureDownloadedCallback();

		virtual void OnLoadedResource(boost::shared_ptr<IResource> resource);

	private:
		boost::shared_ptr<IProceduralPrimitive> primitive;
		int type;
	};

	SculptTextureDownloadedCallback::SculptTextureDownloadedCallback(boost::shared_ptr<IProceduralPrimitive> primitive, int type)
		: primitive(primitive), type(type)
	{
	}

	SculptTextureDownloadedCallback::~SculptTextureDownloadedCallback()
	{
	}

	void SculptTextureDownloadedCallback::OnLoadedResource(boost::shared_ptr<IResource> resource)
	{
		// Cast into a texture.
		boost::shared_ptr<ITexture> texture = boost::dynamic_pointer_cast<ITexture> (resource);
		if(!texture)
			return;

		primitive->SetSculptureType(type);
		primitive->SetSculpture(texture);
	}

	// Material texture downloaded callback.
	class MaterialTextureDownloadedCallback: public IResourceLoadedCallback
	{
	public:
		MaterialTextureDownloadedCallback(const std::string &sampler, boost::shared_ptr<IMaterial> material);
		~MaterialTextureDownloadedCallback();

		virtual void OnLoadedResource(boost::shared_ptr<IResource> resource);

	private:
		std::string sampler;
		boost::shared_ptr<IMaterial> material;
	};

	MaterialTextureDownloadedCallback::MaterialTextureDownloadedCallback(const std::string &sampler, boost::shared_ptr<IMaterial> material)
		: sampler(sampler), material(material)
	{
	}

	MaterialTextureDownloadedCallback::~MaterialTextureDownloadedCallback()
	{
	}

	void MaterialTextureDownloadedCallback::OnLoadedResource(boost::shared_ptr<IResource> resource)
	{
		// Cast into a texture.
		boost::shared_ptr<ITexture> texture = boost::dynamic_pointer_cast<ITexture> (resource);
		if(!texture)
			return;

		// Set the texture and prepare the material.
		material->SetTexture(sampler, texture);
		material->Prepare();
	}

	// ClientSession
	const posix_time::time_duration ClientSession::TerrainFinishTime = posix_time::milliseconds(100);
	ClientSession::ClientSession(boost::shared_ptr<Session> session, boost::shared_ptr<MetaverseNode> node)
		: session(session), node(node)
	{
		// Build the early message table.
		earlyMessageTable.Add("LayerData", &ClientSession::OnLayerData, this);

		// Build the late message table.
		lateMessageTable.Add("AgentMovementComplete", &ClientSession::OnAgentMovementComplete, this);
		lateMessageTable.Add("AvatarAnimation", &ClientSession::OnAvatarAnimation, this);
		lateMessageTable.Add("AvatarAppearance", &ClientSession::OnAvatarAppearance, this);
		lateMessageTable.Add("CoarseLocationUpdate", &ClientSession::OnCoarseLocationUpdate, this);
		lateMessageTable.Add("ImprovedTerseObjectUpdate", &ClientSession::OnImprovedTerseObjectUpdate, this);
		lateMessageTable.Add("KillObject", &ClientSession::OnKillObject, this);
		lateMessageTable.Add("ObjectUpdate", &ClientSession::OnObjectUpdate, this);
		lateMessageTable.Add("ParcelOverlay", &ClientSession::OnParcelOverlay, this);
		lateMessageTable.Add("SimulatorViewerTimeMessage", &ClientSession::OnSimulatorViewerTimeMessage, this);
		lateMessageTable.Add("SimStats", &ClientSession::OnSimStats, this);
		lateMessageTable.Add("ViewerEffect", &ClientSession::OnViewerEffect, this);

		// Clear the terrain height map.
		dirtyTerrain = true;
		dirtyTerrainNormals = true;
		lastTerrainUpdate = posix_time::microsec_clock::universal_time();
	}

	ClientSession::~ClientSession()
	{
	}

	void ClientSession::Connected(boost::shared_ptr<Message> regionHandshake)
	{
		// Create the transfer manager.
		transferManager.reset(new TransferManager(shared_from_this()));

		// Clear the world.
		boost::shared_ptr<WorldManager> world = Engine::Get().GetWorldManager();
		world->Clear();

		// Create the region entity.
		boost::shared_ptr<RemoteRegion> region(new RemoteRegion());
		region->SetId(regionHandshake->GetBlock("RegionInfo2").GetVariable("RegionID").GetValue<uuid> ());

		// Add the region to the world.
		world->AddChild(region);

		// Store a weak reference to the region.
		regionEntity = region;

		// Initialize the region.
		InitializeRegion(regionHandshake);

		// Notify the client module.
		Engine::Get().GetClientModule()->RegionLoaded(region);
	}

	void ClientSession::Disconnected()
	{
	}

	void ClientSession::Frame(float delta)
	{
		// Copy the message queue.
		MessageQueue copy;
		{
			boost::unique_lock<boost::mutex> l(stateMutex);
			copy.swap(messageQueue);
		}

		// Read the messages.
		while(!copy.empty())
		{
			boost::shared_ptr<Message> message = copy.front();
			copy.pop_front();

			// Process transfer messages.
			if(transferManager->ProcessMessage(message))
				continue;

			// Process the messages.
			if(!lateMessageTable[message->GetTemplate()->name](message))
			{
				LogConsole->Warning("Received unknown message: %s", message->GetTemplate()->name.c_str());
			}
		}

		// Update the terrain.
		{
			boost::unique_lock<boost::mutex> l(stateMutex);
			posix_time::ptime now = posix_time::microsec_clock::universal_time();
			if(dirtyTerrain)
			{
				terrain->UploadVertices(0, TerrainHeightMapSize, terrainHeightMap.c_array());
				dirtyTerrain = false;
				dirtyTerrainNormals = true;
				lastTerrainUpdate = now;
			}

			if(now - lastTerrainUpdate > TerrainFinishTime && dirtyTerrainNormals)
			{
				CalculateTerrainNormals();
				dirtyTerrainNormals = false;
			}
		}
	}

	void ClientSession::ReceiveMessage(boost::shared_ptr<Message> message)
	{
		// Store messages not processed earlier in a message queue.
		if(!earlyMessageTable[message->GetTemplate()->name](message))
		{
			boost::unique_lock<boost::mutex> l(stateMutex);
			messageQueue.push_back(message);
		}
	}

	boost::shared_ptr<Entity>
	ClientSession::CreatePlayerController(boost::shared_ptr<Region> region)
	{
		if(!region || regionEntity.lock() != region)
			return boost::shared_ptr<Entity> ();

		boost::shared_ptr<RemotePlayer> player(new RemotePlayer(shared_from_this()));
		region->AddChild(player);
		player->SetId(session->GetAgentId());
		player->Spawn();

		// Move the player.
		OrthoTransform trans;
		trans.SetVector(Vector3(128.0f, 10.0f, 128.0f));
		player->SetTransform(trans);
		return player;
	}

	boost::shared_ptr<Message> ClientSession::CreateMessage(const std::string &name)
	{
		return node->CreateMessage(name);
	}

	void ClientSession::Send(boost::shared_ptr<Message> message)
	{
		session->Send(message);
	}

	const uuid &ClientSession::GetId() const
	{
		return session->GetId();
	}

	const uuid &ClientSession::GetAgentId() const
	{
		return session->GetAgentId();
	}

	void ClientSession::InitializeRegion(boost::shared_ptr<Message> handshake)
	{
		// Get the region.
		boost::shared_ptr<RemoteRegion> region = regionEntity.lock();

		// Get the renderer.
		boost::shared_ptr<IRenderer> renderer = Engine::Get().GetRenderer();

		// Set the region sky.
		renderer->SetSkybox("skybox/day");

		// Get the region info block.
		MessageBlock &infoBlock = handshake->GetBlock("RegionInfo");

		// Read the region info.
		float waterHeight = infoBlock.GetVariable("WaterHeight").GetValue<float> ();

		// Create the region water.
		boost::shared_ptr<Entity> waterEntity(new Entity());
		region->AddChild(waterEntity);

		// Move the water entity.
		OrthoTransform trans;
		trans.SetVector(Vector3(0.0f, waterHeight, 0.0f));
		trans.SetMatrix(FromMetaverseMatrix);
		waterEntity->SetTransform(trans);

		// Create the water mesh.
		boost::shared_ptr<IGeometry> waterMesh = renderer->CreateTerrainPlane(256,256,2,2, Vector3(0.0, 0.0, 1.0), true);
		waterMesh->SetMaterial("WaterMetaverse");
		waterEntity->AddResource(waterMesh->CreateInstance());

		// Create The terrain entity
		boost::shared_ptr<Entity> terrainEnt(new Entity);
		region->AddChild(terrainEnt);

		// Move the terrain.
		trans.SetVector(Vector3(0.0, 0.0, 0.0));
		terrainEnt->SetTransform(trans);

		// Create the terrain material.
		boost::shared_ptr<IMaterial> terrainMaterial = renderer->CreateMaterial("Terrain");
		terrainMaterial->Prepare();

		// Download the terrain textures.
		transferManager->DownloadImage(
			infoBlock.GetVariable("TerrainDetail0").GetValue<uuid> (),
			boost::make_shared<MaterialTextureDownloadedCallback> ("TerrainDetail0", terrainMaterial));
		transferManager->DownloadImage(
					infoBlock.GetVariable("TerrainDetail1").GetValue<uuid> (),
					boost::make_shared<MaterialTextureDownloadedCallback> ("TerrainDetail1", terrainMaterial));
		transferManager->DownloadImage(
					infoBlock.GetVariable("TerrainDetail2").GetValue<uuid> (),
					boost::make_shared<MaterialTextureDownloadedCallback> ("TerrainDetail2", terrainMaterial));
		transferManager->DownloadImage(
					infoBlock.GetVariable("TerrainDetail3").GetValue<uuid> (),
					boost::make_shared<MaterialTextureDownloadedCallback> ("TerrainDetail3", terrainMaterial));

		// Create the terrain mesh.
		terrain = renderer->CreateTerrainPlane(256,256,256,256, Vector3(0.0, 0.0, 1.0), true);
		terrain->SetMaterial(terrainMaterial);

		// Create the terrain instance.
		boost::shared_ptr<IGeometryInstance> terrainInstance = terrain->CreateInstance();
		terrainEnt->AddResource(terrainInstance);

		// Set the terrain custom parameters.
		SVector4 startHeight;
		startHeight.x = infoBlock.GetVariable("TerrainStartHeight00").GetValue<float> ();
		startHeight.y = infoBlock.GetVariable("TerrainStartHeight01").GetValue<float> ();
		startHeight.z = infoBlock.GetVariable("TerrainStartHeight10").GetValue<float> ();
		startHeight.w = infoBlock.GetVariable("TerrainStartHeight11").GetValue<float> ();

		SVector4 heightRange;
		heightRange.x = infoBlock.GetVariable("TerrainHeightRange00").GetValue<float> ();
		heightRange.y = infoBlock.GetVariable("TerrainHeightRange01").GetValue<float> ();
		heightRange.z = infoBlock.GetVariable("TerrainHeightRange10").GetValue<float> ();
		heightRange.w = infoBlock.GetVariable("TerrainHeightRange11").GetValue<float> ();
		terrainInstance->SetCustomParameter(0, startHeight);
		terrainInstance->SetCustomParameter(1, heightRange);

		// Create the sun.
		sun = renderer->CreateLight();
		sun->SetType(ILight::LT_DIRECTIONAL);
		sun->SetDirection(Vector3(1.0, -1.0, 0.0).Normalize());
		region->AddResource(sun);
	}

	inline size_t MapIndex(size_t width, size_t height, int x, int y)
	{
		// Clamp the x coordinate.
		if(x < 0)
			x = 0;
		if(x >= width)
		   x = width-1;

		// Clamp the y coordinate.
		if(y < 0)
			y = 0;
		if(y >= height)
		   y = height-1;

		return width*y + x;
	}

	void ClientSession::CalculateTerrainNormals()
	{
		// Allocate normal buffer.
		size_t numnormals = TerrainSize*TerrainSize*3;
		float *normals = new float[TerrainSize*TerrainSize*3];

		// Calculate the normals.
		for(int y = 0; y < TerrainSize; y++)
		{
			size_t index = y*TerrainSize;
			for(int x = 0; x < TerrainSize; x++, index++)
			{
				// Calculate the normal in that point.
				float dx = terrainHeightMap[MapIndex(TerrainSize, TerrainSize, x + 1, y)]
						- terrainHeightMap[MapIndex(TerrainSize, TerrainSize, x - 1, y)];
				float dy = terrainHeightMap[MapIndex(TerrainSize, TerrainSize, x, y + 1)]
						- terrainHeightMap[MapIndex(TerrainSize, TerrainSize, x, y - 1)];
				dx *= 0.5f;
				dy *= 0.5f;
				Vector3 tan = Vector3(1.0f, 0.0, dx).Normalize();
				Vector3 bi = Vector3(0.0f, 1.0, dy).Normalize();
				Vector3 nor = tan.Cross(bi);

				// Store the normal.
				normals[index*3] = nor.x;
				normals[index*3+1] = nor.y;
				normals[index*3+2] = nor.z;
			}
		}

		// Write the normals.
		terrain->UploadVertices(1, numnormals*sizeof(float), normals);

		// Delete the normals.
		delete [] normals;
	}

	// Early messages.
	void ClientSession::OnLayerData(boost::shared_ptr<Message> message)
	{
		boost::unique_lock<boost::mutex> l(stateMutex);

		// Read the layer type.
		MessageBlock &layerIdBlock = message->GetBlock("LayerID");
		unsigned int layerType = layerIdBlock.GetVariable("Type").GetValue<unsigned int> ();

		// Read the layer data.
		MessageBlock &layerData = message->GetBlock("LayerData");
		MessageVariable &dataVar = layerData.GetVariable("Data");
		size_t dataSize = dataVar.GetBufferSize();
		void *data = dataVar.GetBuffer();

		// Read the layer according to the type.
		if(layerType == LT_LAND)
		{
			// Decode the terrain.
			terrainCodec.DecodeLand(dataSize, data, TerrainSize*TerrainSize, terrainHeightMap.c_array());
			dirtyTerrain = true;
		}
	}

	// Late messages.
	void ClientSession::OnAgentMovementComplete(boost::shared_ptr<Message> message)
	{
    	// Get the player.
    	boost::shared_ptr<Entity> player = Engine::Get().GetWorldManager()->FindChildById(session->GetAgentId());
    	if(!player)
    		return;

    	// Get the Data block
		const MessageBlock &dataBlock = message->GetBlock("Data");

		// Read the position.
		Vector3 position = FromMetaverseMatrix *
				dataBlock.GetVariable("Position").GetValue<Vector3> ();
		//Vector3 lookAt = FromMetaverseMatrix * dataBlock.GetVariable("LookAt").GetValue<Vector3> ();

		// Update the player position.
		OrthoTransform trans;
		trans.SetVector(position);
		player->SetTransform(trans);
	}

	void ClientSession::OnAvatarAnimation(boost::shared_ptr<Message> message)
	{
	}

	void ClientSession::OnAvatarAppearance(boost::shared_ptr<Message> message)
	{
	}

	void ClientSession::OnCoarseLocationUpdate(boost::shared_ptr<Message> message)
	{
	}

	void ClientSession::OnObjectUpdate(boost::shared_ptr<Message> message)
	{
    	// Get the region data block.
    	MessageBlock &regionData = message->GetBlock("RegionData");

    	// Get the object block
    	MessageBlock &objectDataBlock = message->GetBlock("ObjectData");
    	int numupdates = objectDataBlock.GetNumberOfSubBlocks();
    	for(int i = 0; i < numupdates; i++)
    	{
			// Get the fields.
			unsigned int id = objectDataBlock.GetVariable("ID", i).GetValue<unsigned int> ();
			unsigned int parentId = objectDataBlock.GetVariable("ParentID", i).GetValue<unsigned int> ();
			unsigned int pcode = objectDataBlock.GetVariable("PCode", i).GetValue<unsigned int> ();
			uuid fullId = objectDataBlock.GetVariable("FullID", i).GetValue<uuid> ();

			// Ignore objects with null id.
			if(fullId.is_nil())
				continue;

			// Find the object.
			boost::shared_ptr<Entity> entity;
			boost::shared_ptr<RemoteObject> object;

			// Try with the local id first.
			LocalObjectMap::iterator it = localObjectMap.find(id);
			if(it != localObjectMap.end())
			{
				object = it->second.lock();
				entity = object;
			}
			else
			{
				// Now try with the global id.
				entity = Engine::Get().GetWorldManager()->FindChildById(fullId);
			}

			if(!entity)
			{
				// Create the remote object.
				boost::shared_ptr<Region> region = regionEntity.lock();
				if(!region)
					continue;

				object.reset(new RemoteObject());
				object->SetId(fullId);
				region->AddChild(object);

				// Register the object.
				localObjectMap.insert(std::make_pair(id, object));
			}
			else if(!object)
			{
				object = boost::dynamic_pointer_cast<RemoteObject> (entity);
				if(!object)
				{
					LogConsole->Warning("Received invalid object update.");
					continue;
				}

				// Register the local id.
				localObjectMap.insert(std::make_pair(id, object));
			}

			// Avoid setting outdated positions.
			if(message->sequenceNumber < object->lastMovementUpdateSequence)
			{
				printf("Ignore update\n");
				continue;
			}
			object->lastMovementUpdateSequence = message->sequenceNumber;

			Vector3 position;
			Quaternion rotation;
			Vector3 velocity, acceleration;
			Vector3 angularVelocity;

			// Read the object data.
			MessageVariable &objectData = objectDataBlock.GetVariable("ObjectData", i);
			size_t dataSize = objectData.GetBufferSize();
			DataStream in(dataSize, objectData.GetBuffer());
			if(dataSize == 76 || dataSize == 60)
			{
				if(dataSize == 76)
					in.Skip(16); // Ignore the player foot plane.

				in >> position.x >> position.y >> position.z;
				in >> velocity.x >> velocity.y >> velocity.z;
				in >> acceleration.x >> acceleration.y >> acceleration.z;
				in >> rotation.x >> rotation.y >> rotation.z;
				in >> angularVelocity.x >> angularVelocity.y >> angularVelocity.z;

				// Derive the w component.
				float xyzsum = 1.0f - rotation.x*rotation.x - rotation.y*rotation.y - rotation.z*rotation.z;
				rotation.w = (xyzsum > 0) ? sqrt(xyzsum) : 0.0f;
			}
			else if(dataSize == 48 || dataSize == 32)
			{
				//TODO: Decode 16 bit data update.
				printf("TODO: Decode 16 bit data update\n");
				continue;
			}
			else if(dataSize == 16)
			{
				//TODO: Decode 8 bit data update.
				printf("TODO: Decode 8 bit data update\n");
				continue;
			}
			else
			{
				continue;
			}

			// Update the object movement.
			OrthoTransform trans;
			trans.SetVector(FromMetaverseMatrix*position);
			if(!object->IsPlayer())
				trans.SetMatrix(FromMetaverseMatrix*QRotMatrix3(rotation)*ToMetaverseMatrix);
			object->SetTransform(trans);
			object->SetLinearVelocity(FromMetaverseMatrix*velocity);
			object->SetLinearAcceleration(FromMetaverseMatrix*acceleration);
			object->SetAngularVelocity(angularVelocity);

			// Update the parent.
			if(parentId != 0)
			{
				printf("set parent\n");
				LocalObjectMap::const_iterator it = localObjectMap.find(parentId);
				if(it != localObjectMap.end())
				{
					printf("found\n");
					boost::shared_ptr<Entity> parent = it->second.lock();
					if(parent)
						parent->AddChild(object);
				}
			}

			// Update the object primitive.
			switch(pcode)
			{
			case PC_PRIM:
				UpdatePrimitive(object, message, i);
				break;
			case PC_NONE:
			default:
				// Do nothing
				break;
			}
    	}
	}

	void ClientSession::OnImprovedTerseObjectUpdate(boost::shared_ptr<Message> message)
	{
		// Update data
		u8 state, avatar;
		Vector3 position;
		Vector3 velocity;
		Vector3 acceleration;
		Quaternion rotation;
		Vector3 angularVelocity;
		u16 cx, cy, cz, cw; // Compressed values.

    	// Get the object data block.
    	MessageBlock &objectDataBlock = message->GetBlock("ObjectData");
    	int numBlocks = objectDataBlock.GetNumberOfSubBlocks();
    	for(int i = 0; i < numBlocks; i++)
    	{
    		// Get the block data.
    		MessageVariable &dataVar = objectDataBlock.GetVariable("Data", i);

    		// Read the data.
    		size_t dataLen = dataVar.GetBufferSize();
    		DataStream in(dataLen, dataVar.GetBuffer());

    		// Read the local id.
    		u32 localId;
    		in >> localId;

    		// Find the object
    		LocalObjectMap::const_iterator it = localObjectMap.find(localId);
    		if(it == localObjectMap.end())
    			continue;

    		// Read the object entity.
    		boost::shared_ptr<RemoteObject> object = it->second.lock();
    		if(!object)
    			continue;

        	// Avoid setting outdated positions.
        	if(message->sequenceNumber < object->lastMovementUpdateSequence)
        		continue;

        	object->lastMovementUpdateSequence = message->sequenceNumber;

    		// Read the rest of the update
    		in >> state >> avatar;

    		// Collision normal for avatar.
    		if(avatar)
    		{
    			Plane plane;
    			in >> plane.a >> plane.b >> plane.c >> plane.d;
    			// TODO: Do something with this.
    		}

    		// Position.
    		in >> position.x >> position.y >> position.z;

    		// Velocity.
    		in >> cx >> cy >> cz;
    		velocity.x = U16ToFloat(cx, -128.0f, 128.0f);
    		velocity.y = U16ToFloat(cy, -128.0f, 128.0f);
    		velocity.z = U16ToFloat(cz, -128.0f, 128.0f);

    		// Acceleration.
    		in >> cx >> cy >> cz;
    		acceleration.x = U16ToFloat(cx, -64.0f, 64.0f);
    		acceleration.y = U16ToFloat(cy, -64.0f, 64.0f);
    		acceleration.z = U16ToFloat(cz, -64.0f, 64.0f);

    		// Rotation.
    		in >> cx >> cy >> cz >> cw;
    		rotation.x = U16ToFloat(cx, -1.0f, 1.0f);
    		rotation.y = U16ToFloat(cy, -1.0f, 1.0f);
    		rotation.z = U16ToFloat(cz, -1.0f, 1.0f);
    		rotation.w = U16ToFloat(cw, -1.0f, 1.0f);
    		rotation.Normalize();

    		// Angular velocity.
    		in >> cx >> cy >> cz;
    		angularVelocity.x = U16ToFloat(cx, -64.0f, 64.0f);
    		angularVelocity.y = U16ToFloat(cy, -64.0f, 64.0f);
    		angularVelocity.z = U16ToFloat(cz, -64.0f, 64.0f);

    		// Update the object movement.
        	OrthoTransform trans;
        	trans.SetVector(FromMetaverseMatrix*position);
        	if(!object->IsPlayer())
        		trans.SetMatrix(FromMetaverseMatrix*QRotMatrix3(rotation)*ToMetaverseMatrix);
        	object->SetTransform(trans);
        	object->SetLinearVelocity(FromMetaverseMatrix*velocity);
        	object->SetLinearAcceleration(FromMetaverseMatrix*acceleration);
        	object->SetAngularVelocity(angularVelocity);

        	/*if(object->IsPlayer())
        	{
        		printf("up\n");
        		printf("pos: %f %f %f\n", position.x, position.y, position.z);
        		printf("vel %f %f %f\n", velocity.x, velocity.y, velocity.z);
        		printf("acc %f %f %f\n", acceleration.x, acceleration.y, acceleration.z);
        	}*/
    	}
	}

	void ClientSession::OnKillObject(boost::shared_ptr<Message> message)
	{
		const MessageBlock &objectData = message->GetBlock("ObjectData");
		int numobjects = objectData.GetNumberOfSubBlocks();
		for(int i = 0; i < numobjects; i++)
		{
			unsigned int id = objectData.GetVariable("ID", i).GetValue<unsigned int> ();

			// Find the object.
			LocalObjectMap::iterator it = localObjectMap.find(id);
			if(it == localObjectMap.end())
				continue;

			boost::shared_ptr<Entity> object = it->second.lock();
			if(!object)
			{
				// The object was already killed, erase the broken reference.
				localObjectMap.erase(it);
				continue;
			}

			// Remove the object from the parent.
			boost::shared_ptr<Entity> parent = object->GetParent();
			if(parent)
				parent->RemoveChild(object);

			// Clear the object
			object->Clear();

			// Erase the local id.
			localObjectMap.erase(it);
		}
	}

	void ClientSession::OnParcelOverlay(boost::shared_ptr<Message> message)
	{
	}

	void ClientSession::OnSimulatorViewerTimeMessage(boost::shared_ptr<Message> message)
	{
    	// Get the TimeInfo block.
    	MessageBlock &timeInfoBlock = message->GetBlock("TimeInfo");

    	// Get the sun direction.
    	Vector3 direction = timeInfoBlock.GetVariable("SunDirection").GetValue<Vector3> ();
    	if(direction.z > 0.0f)
    		direction *= -1.0f;

    	//printf("sundir %f %f %f\n", direction.x, direction.y, direction.z);
    	sun->SetDirection(FromMetaverseMatrix * direction);
	}

	void ClientSession::OnSimStats(boost::shared_ptr<Message> message)
	{
	}

	void ClientSession::OnViewerEffect(boost::shared_ptr<Message> message)
	{
	}

	// Helper functions.
	void ClientSession::UpdatePrimitive(boost::shared_ptr<Entity> parent, boost::shared_ptr<Message> message, int subblock)
	{
		const int PrimWidth = 64;
		const int PrimHeight = 64;
		const int ProfileMask = 0x0f;
		const int HoleMask = 0xf0;
		const float PathQuanta = 0.01f;
		const float ProfileQuanta = 0.01f;
		const float ScaleQuanta = 0.01f;
		const float ShearQuanta = 0.01f;
		const float SkewQuanta = 0.01f;
		const float TwistQuanta = 0.01f;
		const float RevQuanta = 0.015f;
		const float CutQuanta = 2.0e-5f;

		// Find the existing primitive and light.
		boost::shared_ptr<IProceduralPrimitive> primitive;
		boost::shared_ptr<ILight> light;
		const Entity::Resources &resources = parent->GetResources();
		Entity::Resources::const_iterator it = resources.begin();
		for(; it != resources.end(); it++)
		{
			// Cast the resource into a primitive.
			boost::shared_ptr<IResource> res = *it;
			if(!primitive)
				primitive = boost::dynamic_pointer_cast<IProceduralPrimitive> (res);
			if(!light)
				light = boost::dynamic_pointer_cast<ILight> (res);

			if(primitive && light)
				break;
		}

		// Create a new primitive.
		boost::shared_ptr<IRenderer> renderer = Engine::Get().GetRenderer();
		boost::shared_ptr<IMaterial> material;
		if(!primitive)
		{
			primitive = renderer->CreatePrimitive(PrimWidth, PrimHeight);

			// Create the material.
			material = renderer->CreateMaterial("PrimitiveDefault");
			primitive->SetMaterial(material);

			// Add the primitive and an instance into the object.
			parent->AddResource(primitive);
			parent->AddResource(primitive->CreateInstance());
		}

		// Read the parameters from the message.
		// Use the same structure as OpenSim OSD.
		const MessageBlock &objectData = message->GetBlock("ObjectData");

		JsonObject path;
		path["begin"] = objectData.GetVariable("PathBegin", subblock).GetValue<unsigned int> () * CutQuanta;
		path["curve"] = objectData.GetVariable("PathCurve", subblock).GetValue<unsigned int> ();
		path["end"] = 1.0f - objectData.GetVariable("PathEnd", subblock).GetValue<unsigned int> () * CutQuanta;
		path["radius_offset"] = objectData.GetVariable("PathRadiusOffset", subblock).GetValue<int> () * PathQuanta;
		path["revolutions"] = objectData.GetVariable("PathRevolutions", subblock).GetValue<unsigned int> () * RevQuanta;
		path["scale_x"] = (objectData.GetVariable("PathScaleX", subblock).GetValue<unsigned int> () - 100)* ScaleQuanta;
		path["scale_y"] = (objectData.GetVariable("PathScaleY", subblock).GetValue<unsigned int> () - 100)* ScaleQuanta;
		path["shear_x"] = objectData.GetVariable("PathShearX", subblock).GetValue<unsigned int> () * ShearQuanta;
		path["shear_y"] = objectData.GetVariable("PathShearY", subblock).GetValue<unsigned int> () * ShearQuanta;
		path["skew"] = objectData.GetVariable("PathSkew", subblock).GetValue<int> () * SkewQuanta;
		path["taper_x"] = objectData.GetVariable("PathTaperX", subblock).GetValue<int> () * PathQuanta;
		path["taper_y"] = objectData.GetVariable("PathTaperY", subblock).GetValue<int> () * PathQuanta;
		path["twist"] = objectData.GetVariable("PathTwist", subblock).GetValue<int> () * TwistQuanta;
		path["twist_begin"] = objectData.GetVariable("PathTwistBegin", subblock).GetValue<int> () * TwistQuanta;

		JsonObject profile;
		profile["begin"] = objectData.GetVariable("ProfileBegin", subblock).GetValue<unsigned int> () * CutQuanta;
		profile["curve"] = objectData.GetVariable("ProfileCurve", subblock).GetValue<unsigned int> () & ProfileMask;
		profile["hole"] = objectData.GetVariable("ProfileCurve", subblock).GetValue<unsigned int> () & HoleMask;
		profile["end"] = 1.0f - objectData.GetVariable("ProfileEnd", subblock).GetValue<unsigned int> () * CutQuanta;
		profile["hollow"] = objectData.GetVariable("ProfileHollow", subblock).GetValue<unsigned int> () * CutQuanta;

		JsonObject volume;
		volume["path"] = path;
		volume["profile"] = profile;

		JsonObject prim;
		prim["volume"] = volume;
		prim["name"] = "Object";
		prim["scale"] = objectData.GetVariable("Scale", subblock).GetValue<Vector3> ();
		prim["rotation"] = FromMetaverseMatrix.ToQuaternion();


		// Read the extra parameters.
		const MessageVariable &extraParams = objectData.GetVariable("ExtraParams", subblock);
		bool isSculpted = false;
		bool isLight = false;
		bool isFlexi = false;
		uuid sculptTexture;
		u8 sculptType;
		if(extraParams.GetBufferSize() > 0)
		{
			DataStream in(extraParams.GetBufferSize(), (void*)extraParams.GetBuffer());
			u8 paramCount;
			in >> paramCount;
			for(int i = 0; i < paramCount; i++)
			{
				u16 paramType;
				u32 paramLength;
				in >> paramType >> paramLength;

				if(paramType == EPT_FLEXIBLE &&
				   paramLength >= 16)
				{
					u8 tension, drag, gravity, wind;
					Vector3 force;
					in >> tension >> drag >> gravity >> wind;
					in >> force.x >> force.y >> force.z;

					// Decode the softness.
					int softness = ((tension & 0x80) >> 6) | ((drag & 0x80) >> 7);

					// Write the flexi-data.
					JsonObject flexiData;
					flexiData["softness"] = softness;
					flexiData["drag"] = (drag & 0x7f) * 0.1f;
					flexiData["gravity"] = gravity*0.1f - 10.0f;
					flexiData["wind"] = wind * 0.1f;
					flexiData["force"] = force;
					prim["flex"] = flexiData;

					isFlexi = true;
					in.Skip(paramLength - 16);
				}
				else if(paramType == EPT_LIGHT &&
						paramLength >= 16)
				{
					u8 colorr, colorg, colorb, colora;
					float radius, cutoff, falloff;
					in >> colorr >> colorg >> colorb >> colora;
					in >> radius >> cutoff >> falloff;

					// Create the light.
					if(!light)
					{
						light = renderer->CreateLight();
						parent->AddResource(light);
					}

					// Set the light parameters.
					Color color(colorr/255.0f, colorg/255.0f, colorb/255.0f, colora/255.0f);
					light->SetDiffuseColor(color);
					light->SetSpecularColor(color);
					light->SetAttenuation(radius, 1.0f, 0.1f, 0.0f);
					light->SetType(ILight::LT_POINT);

					// TODO: Set the other parameters.

					isLight = true;
					in.Skip(paramLength - 16);
				}
				else if(paramType == EPT_SCULPT &&
						paramLength >= 17)
				{
					isSculpted = true;
					in >> sculptTexture >> sculptType;

					JsonObject sculptData;
					sculptData["texture"] = boost::lexical_cast<std::string> (sculptTexture);
					sculptData["type"] = sculptType;
					prim["sculpt"] = sculptData;
					in.Skip(paramLength - 17);
				}
				else
				{
					in.Skip(paramLength);
				}
			}
		}

		// Check if the object is not a light anymore.
		if(!isLight && light)
			parent->RemoveResource(light);

		// Set the primitive parameters.
		primitive->SetParameters(prim);

		if(isSculpted)
		{
			// Download the sculpt texture.
			boost::shared_ptr<IResourceLoadedCallback> callback(new SculptTextureDownloadedCallback(primitive, sculptType));
			transferManager->DownloadImage(sculptTexture, callback);
		}

		// Read the texture data.
		if(!material)
			material = primitive->GetMaterial();

		ReadTexture(message, subblock, material);
	}

	inline bool ReadFaceBitfield(DataStream &in, u32 &faceBits, u32 &bitfieldSize)
	{
		u8 b;
		faceBits = 0;
		bitfieldSize = 0;

		do
		{
			in >> b;
			faceBits = (faceBits << 7) | (b & 0x7f);
			bitfieldSize += 7;
		}while(b & 0x80);

		return faceBits != 0;
	}

	inline Color ReadColorInverted(DataStream &in)
	{
		u8 cr, cg, cb, ca;
		in >> cr >> cg >> cb >> ca;

		Color color;
		color.r = float(255 - cr)/255.0f;
		color.g = float(255 - cg)/255.0f;
		color.b = float(255 - cb)/255.0f;
		color.a = float(255 - ca)/255.0f;
		return color;
	}

	void ClientSession::ReadTexture(boost::shared_ptr<Message> message, int subblock, boost::shared_ptr<IMaterial> material)
	{
		// Get the data block.
		const MessageBlock &objectData = message->GetBlock("ObjectData");

		// Read the texture entry.
		const MessageVariable &textureEntry = objectData.GetVariable("TextureEntry", subblock);
		size_t entryLen = textureEntry.GetBufferSize();
		if(entryLen >= 16)
		{
			assert(material);
			DataStream in(entryLen, (void*)textureEntry.GetBuffer());
			uuid texture;
			in >> texture;

			// Download the texture.
			boost::shared_ptr<IResourceLoadedCallback> callback(new MaterialTextureDownloadedCallback("DiffuseMap", material));
			transferManager->DownloadImage(texture, callback);

			u32 faceBits;
			u32 bitfieldSize;
			while(ReadFaceBitfield(in, faceBits, bitfieldSize))
			{
				uuid temp;
				in >> temp;
			}

			// Read the colors.
			Color defaultColor = ReadColorInverted(in);
			material->SetAmbient(defaultColor);
			material->SetDiffuse(defaultColor);
			//material->SetSpecular(defaultColor);
			material->SetSpecular(Color());
		}

	}
} // namespace XernMetaverse
